<?php

/**
 * Tree utility. 
 *
 */
abstract class Kernel_Util_Tree
{
    
    /**
     * Convert in-line array entries in the form of "id => parent_id" to 
     * the tree array
     * 
     * @param array
     */
    static public function convert($set)
    {
        return self::_convert($set, 0);
    }
    
    /**
    * Return children for specified node. 
    * 
    * @param array      $set
    * @param integer    $node ID
    * @return array
    */
    static public function children($set, $node) 
    {
        return self::_convert($set, $node);
    }
    
    /**
    * Return all the non-children for the specified node. If flag is true 
    * then exclude itself too. 
    * 
    * @param array      $set
    * @param integer    $node ID
    * @param boolean    $self_exclude = true
    * @return array
    */
    static public function non_children($set, $node = null, $self_exclude = true) 
    {
        return is_null($node) 
            ? self::_convert($set, 0) 
            : self::_convert($set, 0, array(__CLASS__, '_non_children'), 
                array('node' => $node, 'self_exclude' => $self_exclude));
    }
    
    /**
    * @desc 
    * 
    */
    static private function _convert($set, $root, $callback = null, $params = array()) 
    {
        // create temporary tree root
        $root = array('id' => $root);
        
        // check callback function  
        if (!is_null($callback) && !is_callable($callback)) {
            trigger_error("Declared callback function $callback is not callable", E_USER_ERROR);
        }
        // add leaves and branches
        self::_addToTree($set, $root, $callback, $params);
        
        // return children of the temporary root
        return $root['children'];
    }

    /**
     * Add elements.
     *
     * @param   array   $stack
     * @param   array   $node
     */
    static private function _addToTree($stack, &$node, $callback = null, $params = array())
    {
        $children = array();

        foreach ($stack as $child)
        {
            if (!is_null($callback)) {
                $result = call_user_func($callback, &$child, $params);
                if (false === $result) { continue; }
            }
            
            if ($child['parent_id'] == $node['id']) {
                self::_addToTree($stack, $child, $callback, $params);

                if (array_key_exists('active', $child) && $child['active']) {
                    $node['active'] = true;
                }

                $children[] = $child;
            }
        }
        
        // attache nested sets to the parent
        $node['children'] = $children;
    }
    
    /**
    * Callback function to exclude children (and self node). 
    * 
    * @param array
    * @param mixed
    */
    static private function _non_children($child, $params) 
    {
        extract($params);
        
        return $self_exclude 
            ? !($child['parent_id'] == $node || $child['id'] == $node) 
            : !($child['parent_id'] == $node);
    }
    
}